/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package DAO;

import DTO.PacketDTO;
import MAIN.GeneratePublicPrivateKeys;
import MAIN.Hash_SHA256;
import MAIN.usc_Certificate;
import MAIN.usc_RSAKeyPair;
import static MAIN.usc_sessionkey_mac.Decrypt;
import static MAIN.usc_sessionkey_mac.Encrypt;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.Socket;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 *
 * @author Anonymous
 */
public class ServerThread extends Thread{
    private SecretKey key;//ko su dung
    
    private static String secret_key = "default";
    public static boolean use_secret_key = false;//=true, after Certificate period. 
    
    private Socket clientSocket;
    public ServerThread(Socket cl) {//gán socket
        super("ServerThread");
        clientSocket = cl;
    }
    @Override
    public void run()
    {
        try { 
                //Đầu tiên gán giá trị lấy được vào gói tin nhận
                DTO.PacketDTO pkdtor=new DTO.PacketDTO();
                pkdtor=(DTO.PacketDTO)receiveData(clientSocket);//lấy giá trị từ client
                if(pkdtor==null)
                {
                    return;
                }
                if(pkdtor.getCommand()==1)//nếu lệnh là 1 : lấy danh sách chuyến xe
                {
                    //Lấy danh sách chuyến xe chưa đi từ sql server vào biến Lcxdto
                    BUS.ChuyenXeBUS cxbus=new BUS.ChuyenXeBUS();
                    List<DTO.ChuyenXeDTO> Lcxdto= new ArrayList<DTO.ChuyenXeDTO>();
                    Lcxdto=cxbus.dsChuyenXecl();
                    
                    //Đưa dữ liệu danh sách chuyến xe vào gói tin gửi pkdtos
                    DTO.PacketDTO pkdtos=new DTO.PacketDTO();
                    pkdtos.setData(Lcxdto);//gán giá trị
                    
                    if(Lcxdto!=null)//Nếu danh sách không trống thì gán flag là true
                        pkdtos.setFlag(true);
                    else
                        //ngược lại gán false
                        pkdtos.setFlag(false);
                    
                    sendData(clientSocket,pkdtos);//gửi gói tin cho client
                }
                if(pkdtor.getCommand()==2)//nếu lệnh là 2 : đặt vé
                {
                    
                    BUS.DatVeBUS dvbus=new BUS.DatVeBUS();
                    DTO.DatVeDTO dvdto= new DTO.DatVeDTO();
                    dvdto=(DTO.DatVeDTO)pkdtor.getData();//gán giá trị đặt vé đã nhận từ pkdtor vào biến dvdto
                    
                    //Đưa gói tin xác nhận thành công hay thất bại cho client
                    DTO.PacketDTO pkdtos=new DTO.PacketDTO();
                    pkdtos.setFlag(dvbus.ThemDatVe(dvdto));//tiến hành đặt vé và gán flag vào gói tin 
                    
                    sendData(clientSocket,pkdtos);//gửi gói tin cho client
                }
                if(pkdtor.getCommand()==101)//nếu lệnh là 101: nhan GUEST.cer
                {   
                    //Nhan GUEST.cer
                    Certificate guest_cer = (Certificate)pkdtor.getData();
                    System.out.println("== == == == == == == == == ==");
                    System.out.println("Client has sent you GUEST.cer");
                    boolean f1 = MAIN.usc_Certificate.validateCertificate(guest_cer);
                    if(f1 == true)
                    {
                        System.out.println("You have had GUEST.cer. It is VALID.");
                        //Luu GUEST.cer
                        FileOutputStream os = new FileOutputStream("GUEST.cer");
                        os.write(guest_cer.getEncoded());
                        os.close(); 
                        //Gui SERVER.cer
                        //**Tao public va private key
                        GeneratePublicPrivateKeys gk = new GeneratePublicPrivateKeys();
                        gk.generateKeys();
                        PrivateKey privk = gk.readPriKeyFromFile("private.key");
                        PublicKey pubk = gk.readPubKeyFromFile("public.key");
                        //System.out.println(privk);
                        //System.out.println(pubk);

                        //**Tao Cert tuong ung voi public key
                        usc_Certificate.privKey = privk;
                        usc_Certificate.pubKey = pubk;
                        usc_Certificate.createCertificate("SERVER.cer");
                        
                        DTO.PacketDTO pkdtos=new DTO.PacketDTO();
                        pkdtos.setFlag(true);                        
                        FileInputStream is = new FileInputStream(new File("SERVER.cer"));
                        CertificateFactory cf = CertificateFactory.getInstance("X.509");
                        Certificate server_cer = cf.generateCertificate(is);
                        pkdtos.setData(server_cer);
                        sendData(clientSocket,pkdtos);
                    }
                    else
                    {
                        System.out.println("You have had GUEST.cer. It is INVALID.");
                        //Gui canh bao toi Client va ngat ket noi.
                        DTO.PacketDTO pkdtos=new DTO.PacketDTO();
                        pkdtos.setFlag(false);
                        pkdtos.setData("Invalid Certificate.");
                        sendData(clientSocket,pkdtos);
                    }
                    

                }
                if(pkdtor.getCommand()==102)//nếu lệnh là 102: Client từ chối SERVER.cer va ngat ket noi
                {
                    System.out.println("Client says: " + pkdtor.getData().toString());
                    //đóng kết nối: thoat ra khoi if.
                    clientSocket.close();
                    return;
                }
                
                if(pkdtor.getCommand()==103)//lenh 103: Kiem Tra Public-Private key, yeu cau Secret-key
                {
                    //Kiem tra Public-Private Key cua Client
                    System.out.println("== == == == == == == == == ==");                  
                    FileInputStream is = new FileInputStream(new File("GUEST.cer"));
                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
                    Certificate client_cer = cf.generateCertificate(is);
                    PublicKey client_pubkey = client_cer.getPublicKey();
                    //System.out.println("Client's Public key: " + client_pubkey);
                    
                    //Nhan goi tin tu Client.
                    byte[][] data_1_C = (byte[][])pkdtor.getData();
                    String plaintext_C = new String(data_1_C[0],"UTF-8");
                    System.out.println("Client has sent a plaintext: " + plaintext_C);
                    //******                 
                    byte[] encrypted_digest_1 = data_1_C[1];
                    //*****
                    byte[] decrypted_digest_1 = usc_RSAKeyPair.PAIR2_Decrypt(encrypted_digest_1, usc_Certificate.privKey);
                    System.out.println("-->Use Server PRIVATE KEY to decrypt.");
                    String digest_1 = new String(decrypted_digest_1,"UTF-8");
                    System.out.println("With SHA-256 Digest: " + digest_1);
                    System.out.print("Comparing SHA-256 Digest: ");
                    String hash_result = MAIN.Hash_SHA256.sha256(plaintext_C);
                    if(hash_result.equals(digest_1)==false)
                    {
                        System.out.println("NOT CORRECT! That is not my guest. Disconect...");
                        pkdtor = new PacketDTO();
                        pkdtor.setFlag(false);
                        pkdtor.setData("Client Key Pair is not valid. Disconect!");
                        sendData(clientSocket,pkdtor);
                        clientSocket.close();
                        return;
                    }
                    
                    System.out.println("CORRECT! That is my GUEST.");
                    System.out.println("-----");
                    
                    //Kiem tra Public-Private Key cua Server, gui Secret-key cho Client
                    String plaintext_S = MAIN.usc_sessionkey_mac.randomString(50);
                    secret_key = MAIN.usc_sessionkey_mac.xorMessage(plaintext_S, plaintext_C);
                    secret_key = Hash_SHA256.sha256(secret_key);
                    String encrypted_secret_key = MAIN.usc_sessionkey_mac.xorMessage(secret_key, plaintext_C);
                    plaintext_S = encrypted_secret_key;
                    byte[] encrypted_plaintext_S = usc_RSAKeyPair.PAIR2_Encrypt(
                            plaintext_S.getBytes(Charset.forName("UTF-8")), client_pubkey);
                    String digest_2 = Hash_SHA256.sha256(plaintext_S);
                    byte[] encrypted_digest_2 = usc_RSAKeyPair.PAIR2_Encrypt(
                            digest_2.getBytes(Charset.forName("UTF-8")), client_pubkey);
                            
                    byte[][] data_2_S = new byte[2][];
                    //data_2_S[0] = plaintext_S.getBytes(Charset.forName("UTF-8"));
                    data_2_S[0] = encrypted_plaintext_S;
                    data_2_S[1] = encrypted_digest_2;
                    pkdtor = new PacketDTO();
                    pkdtor.setFlag(true);
                    pkdtor.setData((Object)data_2_S);
                    sendData(clientSocket, pkdtor);
                    System.out.println("You have sent to Client a plaintext: " + plaintext_S);
                    System.out.println("And it's SHA-256 digest: " + digest_2);                       
                    System.out.println("-->Use Client PUBLIC KEY to encrypt.");
                    System.out.println("SECRET KEY: " + secret_key);

                }
                if(pkdtor.getCommand()==104)//ket qua kt Server public key ben Client khong thanh cong.
                {                     
                    System.out.println("Client says: " + pkdtor.getData().toString());
                    //đóng kết nối: thoat ra khoi if.
                    clientSocket.close();
                    return;
                }
                if(pkdtor.getCommand()==105)//phan hoi da nhan Secret key tu Client.
                {
                    System.out.println();
                    System.out.println("-> Secret Key was validated by CLient.");
                    System.out.println("Now. use_secret_key is ON.");
                    use_secret_key = true;    
                    System.out.println("== == == == == == == == == ==");  
                }
                if(pkdtor.getCommand()==106)//Test Secret-key: Command 106
                {                           
                    Object obj = pkdtor.getData();
                    String s3 = obj.toString();
                    System.out.println("Receive: " + s3);
                    
                    String s4 = "Yes. I give praise to Father.";
                    pkdtor.setData(s4);
                    sendData(clientSocket, pkdtor);
                    System.out.println("Send: " + s4);
                    
                }
                
                clientSocket.close();//đóng kết nối
        } catch (IOException ex) {
            Logger.getLogger(Connect.class.getName()).log(Level.SEVERE, null, ex);
        } catch (CertificateException ex) {
            Logger.getLogger(ServerThread.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(ServerThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    //Hàm chuyển dữ liệu đến cient đầu vào là socket client và gói tin pkdto
    public static boolean sendData(Socket clientSocket,DTO.PacketDTO pkdto)
    {
        try {
            String secret_key2 = "default";
            if(use_secret_key==true)
            {
                secret_key2 = secret_key;
            }
            byte[] encrypt = Encrypt(secret_key2, pkdto);
            //System.out.println("sendData>secret_key: " + secret_key2);
            OutputStream os = clientSocket.getOutputStream();   
            ObjectOutputStream oos = new ObjectOutputStream(os);  
            oos.writeObject(encrypt);  
            /*
            OutputStream os = clientSocket.getOutputStream();  
            ObjectOutputStream oos = new ObjectOutputStream(os);  
            
            oos.writeObject(pkdto);  
            */
            return true;
        } catch (IOException ex) {
            Logger.getLogger(Connect.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(ServerThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    
        return false;
    }
    
    //Hàm nhận dữ liệu từ client đầu ra là pkdto tùy vào bên client, mặc định là DTO.PacketDTO
    public static Object receiveData(Socket clientSocket)
    {
        try {
            InputStream is = clientSocket.getInputStream();  
            ObjectInputStream ois = new ObjectInputStream(is);  
            Object obj = ois.readObject();              
            byte[] encrypt = (byte[])obj;
            Object pkdto = Decrypt(secret_key, encrypt);
            /*
            InputStream is = clientSocket.getInputStream();  
            ObjectInputStream ois = new ObjectInputStream(is);  
            Object pkdto = ois.readObject();  
            */
            return pkdto;
        } catch (IOException | ClassNotFoundException ex) {
            //Logger.getLogger(Connect.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            //Logger.getLogger(ServerThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    
        return null;
    }
    
     public void generateKeys() {

        try {

            // Get the public/private key pair
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
            keyGen.initialize(1024);
            KeyPair keyPair = keyGen.genKeyPair();
            PrivateKey privateKey = keyPair.getPrivate();
            PublicKey  publicKey  = keyPair.getPublic();

            // Get the bytes of the public and private keys
            byte[] privateKeyBytes = privateKey.getEncoded();
            byte[] publicKeyBytes  = publicKey.getEncoded();
			
            //converting byte arrays to strings
            String privateString = new String(privateKeyBytes);
            String publicString = new String(publicKeyBytes);
						
            KeyFactory fact = KeyFactory.getInstance("RSA");
            RSAPublicKeySpec pub = fact.getKeySpec(keyPair.getPublic(),RSAPublicKeySpec.class);
            RSAPrivateKeySpec priv = fact.getKeySpec(keyPair.getPrivate(),RSAPrivateKeySpec.class);

            saveToFile("public.key", pub.getModulus(), pub.getPublicExponent());
            saveToFile("private.key", priv.getModulus(), priv.getPrivateExponent());

        } catch (InvalidKeySpecException specException) {

            System.out.println("Exception");
            System.out.println("Invalid Key Spec Exception");
            
        } catch (NoSuchAlgorithmException e) {

            System.out.println("Exception");
            System.out.println("No such algorithm: " + "RSA");

        }
		
		catch(IOException e) {
			System.out.println("IO exception");
		}	

    }
 
    private static void saveToFile(String fileName, BigInteger mod, BigInteger exp) throws IOException {
      try (ObjectOutputStream oout = new ObjectOutputStream(
                 new BufferedOutputStream(new FileOutputStream(fileName)))) {
            oout.writeObject(mod);
            oout.writeObject(exp);
      } catch (Exception e) {
            throw new IOException("Unexpected error", e);
      }
    }
    
    public boolean receiveExKey(Object data) {
       //Object data = receiveData(clientSocket);
        byte[] command = new byte[4];
        
        try {
            ObjectInputStream objectInput = new ObjectInputStream((ObjectInputStream) data);
            objectInput.read(command);
                        
            BigInteger m = (BigInteger) objectInput.readObject();
            BigInteger e = (BigInteger) objectInput.readObject();
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
            KeyFactory fact = KeyFactory.getInstance("RSA");
            PublicKey pubKey = fact.generatePublic(keySpec);
            
            saveToFile("session.key", m, e);
            return true;
            
        } catch (IOException ex) {
            Logger.getLogger(GeneratePublicPrivateKeys.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(GeneratePublicPrivateKeys.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(GeneratePublicPrivateKeys.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(GeneratePublicPrivateKeys.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return false;       
    }
    
    private PublicKey readPubKeyFromFile(String keyFileName) throws IOException {
      FileInputStream in = new FileInputStream(keyFileName);
      ObjectInputStream oin =
            new ObjectInputStream(new BufferedInputStream(in));
      try {
            BigInteger m = (BigInteger) oin.readObject();
            BigInteger e = (BigInteger) oin.readObject();
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
            KeyFactory fact = KeyFactory.getInstance("RSA");
            PublicKey pubKey = fact.generatePublic(keySpec);
            return pubKey;
      } catch (Exception e) {
            throw new RuntimeException("Spurious serialisation error", e);
      } finally {
            oin.close();
      }
    }
    public boolean sendPubKey() {

        try {
            PublicKey pubKey = readPubKeyFromFile("public.key");
            byte[] pubKeyData = pubKey.getEncoded();
            DTO.PacketDTO pkdto=new DTO.PacketDTO();
            pkdto.setData(pubKeyData);
            ServerThread.sendData(clientSocket, pkdto);
            
            return true;
        } catch (IOException ex) {
            Logger.getLogger(GeneratePublicPrivateKeys.class.getName()).log(Level.SEVERE, null, ex);
        }

 
        return false;
        
    }
    
    public boolean receivePubKey() {
        Object data = receiveData(clientSocket);
        byte[] command = new byte[4];
        
        try {
            ObjectInputStream objectInput = new ObjectInputStream((ObjectInputStream) data);
            objectInput.read(command);
                        
            BigInteger m = (BigInteger) objectInput.readObject();
            BigInteger e = (BigInteger) objectInput.readObject();
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
            KeyFactory fact = KeyFactory.getInstance("RSA");
            PublicKey pubKey = fact.generatePublic(keySpec);
            
            saveToFile("public.client.key", m, e);
            return true;
            
        } catch (IOException ex) {
            Logger.getLogger(GeneratePublicPrivateKeys.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(GeneratePublicPrivateKeys.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(GeneratePublicPrivateKeys.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(GeneratePublicPrivateKeys.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return false;
    }
    
    private static PrivateKey readPriKeyFromFile(String keyFileName) throws IOException {

      FileInputStream in = new FileInputStream(keyFileName);
      ObjectInputStream oin =
            new ObjectInputStream(new BufferedInputStream(in));
      try {
            BigInteger m = (BigInteger) oin.readObject();
            BigInteger e = (BigInteger) oin.readObject();
            RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(m, e);
            KeyFactory fact = KeyFactory.getInstance("RSA");
            PrivateKey priKey = fact.generatePrivate(keySpec);
            return priKey;
      } catch (Exception e) {
            throw new RuntimeException("Spurious serialisation error", e);
      } finally {
            oin.close();
      }
    }
    
    private byte[] decryptSessionKey() {
        try {
               PrivateKey sessionKey = readPriKeyFromFile("session.key");
               byte[] sessionKeyData = sessionKey.getEncoded();

               PrivateKey priKeyServer = readPriKeyFromFile("private.key");
               byte[] priKeyData = priKeyServer.getEncoded();
               
               PublicKey pubKeyClient = readPubKeyFromFile("public.client.key");
               byte[] pubKeyData = pubKeyClient.getEncoded();

               //decypt by private key of server
               Cipher cipherPri = Cipher.getInstance("RSA/ECB/PKCS1Padding");
               cipherPri.init(Cipher.DECRYPT_MODE, priKeyServer);
               byte[] cipherDataPri = cipherPri.doFinal(sessionKeyData);

               //decrypt by public key of client
               Cipher cipherPub = Cipher.getInstance("RSA/ECB/PKSC1Padding");
               cipherPub.init(Cipher.DECRYPT_MODE, pubKeyClient);
               byte[] cipherDataPub = cipherPub.doFinal(cipherDataPri);

                return cipherDataPub;
           }
           catch (Exception e) {
               e.printStackTrace();
           }
        return null;
    }
    
    private byte[] encrypt(byte[] data) throws IllegalBlockSizeException,
	    BadPaddingException, NoSuchAlgorithmException,
	    NoSuchPaddingException, InvalidKeyException,
	    UnsupportedEncodingException {
        
        key = new SecretKeySpec(decryptSessionKey(), 0, decryptSessionKey().length, "DES/ECB/PKCS5Padding");
	// Get a cipher object.
	Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
	cipher.init(Cipher.ENCRYPT_MODE, key);
 
	// encrypt using the cypher
	byte[] raw = cipher.doFinal(data);
 
	return raw;
    }
    
      private byte[] decrypt(byte[] data) throws InvalidKeyException,
	    NoSuchAlgorithmException, NoSuchPaddingException,
	    IllegalBlockSizeException, BadPaddingException, IOException {
 
          
           key = new SecretKeySpec(decryptSessionKey(), 0, decryptSessionKey().length, "DES/ECB/PKCS5Padding");
            // Get a cipher object.
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key);

            //decode the message
            byte[] result = cipher.doFinal(data);

            return result;
    }
    
}  